Skip to content

feat: add stream subcommand for live terminal session broadcasting#278

Merged
ibigbug merged 7 commits into
mainfrom
copilot/support-live-streaming
Mar 11, 2026
Merged

feat: add stream subcommand for live terminal session broadcasting#278
ibigbug merged 7 commits into
mainfrom
copilot/support-live-streaming

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 10, 2026

Adds a stream subcommand to PowerSession-rs that enables live terminal session broadcasting to an asciinema-compatible server over WebSocket.

Changes

New stream subcommand (src/commands/stream.rs)

  • Connects to an asciinema server via WebSocket (TLS via rustls)
  • Broadcasts terminal output as asciicast o events in real time
  • Sends an initial r (reset) event with the current terminal size
  • Runs a background WS read loop to handle Ping/Close frames and keep the connection alive
  • Defaults to powershell.exe (or $SHELL on non-Windows) when no --command is given

Extended API client (src/commands/api/asciinema.rs)

  • create_stream(cols, rows) — POST to /api/streams to create a new live stream
  • get_stream_ws_url(id) — derive the producer WebSocket URL from the server base URL
  • get_auth_header() — return a Basic base64-encoded Authorization header
  • Unit tests for get_stream_ws_url (http/https, trailing-slash handling) and get_auth_header

CLI integration (src/main.rs)

  • New stream subcommand with --command/-c (optional) and --id (optional, reconnects to existing stream)
  • Uses WindowsTerminal::console_size() (lightweight, no PTY allocation) to read terminal dimensions

Dependencies

  • tungstenite 0.28 with rustls-tls-webpki-roots — synchronous WebSocket client
  • reqwest updated to include json feature
Original prompt

This section details on the original issue you should resolve

<issue_title>Support live streaming like asciinema</issue_title>
<issue_description>Image</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: ibigbug <543405+ibigbug@users.noreply.github.com>
Copilot AI changed the title [WIP] Support live streaming like asciinema feat: add stream subcommand for live terminal session broadcasting Mar 10, 2026
@ibigbug ibigbug requested a review from Copilot March 10, 2026 04:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new PowerSession stream subcommand to broadcast a live ConPTY-backed terminal session to an asciinema-compatible server over WebSocket, including API support for creating/reconnecting to streams.

Changes:

  • Introduces a stream CLI subcommand with --command/-c and --id to create or reconnect to live streams.
  • Extends the ApiService/Asciinema API client with live-stream endpoints and helper methods (create stream, build WS URL, auth header).
  • Adds synchronous WebSocket support via tungstenite (rustls) and implements the streaming loop (reset + stdout events).

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Cargo.toml Adds tungstenite dependency for WSS streaming support.
Cargo.lock Locks new transitive deps for tungstenite/rustls stack.
src/main.rs Wires up the stream subcommand and create/reconnect flow.
src/commands/stream.rs Implements live streaming (PTY IO threads + WS text frames).
src/commands/mod.rs Registers and re-exports the new Stream command and ApiService.
src/commands/api/mod.rs Extends ApiService with stream methods and defines StreamInfo.
src/commands/api/asciinema.rs Implements stream creation, WS URL derivation, and WS auth header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/main.rs
Comment on lines +239 to +243
#[cfg(windows)]
let (cols, rows) = {
let t = crate::terminal::WindowsTerminal::new(None);
(t.width as u16, t.height as u16)
};
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a WindowsTerminal::new(None) here just to read width/height has significant side effects (it creates a ConPTY and switches the console into raw mode). This introduces an extra PTY allocation and can alter the user’s console state before streaming even starts. Prefer a lightweight terminal-size query (e.g., expose a dedicated get_console_size helper from the terminal module) or pass a default size to create_stream and rely on the subsequent reset event to communicate the actual size.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/stream.rs
Comment on lines +53 to +55
println!("Streaming. Watch at: {}", self.stream_url);
println!("Press Ctrl+D to stop.");
self.stream();
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instruction Press Ctrl+D to stop. is not accurate on Windows consoles (EOF is typically Ctrl+Z then Enter), and the streaming session actually ends when the spawned shell/process exits. Update the prompt to reflect the correct key sequence / behavior, or document that the user should exit the shell/command to end streaming.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/stream.rs
Comment on lines +72 to +74
let (mut ws, _) =
tungstenite::connect(request).expect("failed to connect to stream server");

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The WebSocket is only ever written to; no read loop is running. If the server sends control frames (e.g., Ping/Close), the client may not respond, which can cause the connection to be dropped or to hang during shutdown. Consider running a small read loop (in this or a separate thread) to handle Ping/Pong and Close frames and to drive the connection state machine while streaming.

Copilot uses AI. Check for mistakes.
Comment on lines +197 to +254
fn create_stream(&self, cols: u16, rows: u16) -> Option<StreamInfo> {
#[derive(Deserialize)]
struct CreateStreamResponse {
id: String,
url: String,
ws_producer_url: String,
}

let stream_url = format!("{}/api/streams", &self.config.api_server);
let body = serde_json::json!({ "cols": cols, "rows": rows });
let res = match self
.http_client
.post(stream_url)
.json(&body)
.send()
{
Ok(r) => r,
Err(e) => {
println!("Failed to reach stream server: {}", e);
return None;
}
};

if res.status().is_success() {
match res.json::<CreateStreamResponse>() {
Ok(resp) => Some(StreamInfo {
id: resp.id,
url: resp.url,
ws_producer_url: resp.ws_producer_url,
}),
Err(e) => {
println!("Failed to parse stream response: {}", e);
None
}
}
} else {
println!("Failed to create stream:");
println!("{}", res.text().unwrap_or_default());
None
}
}

fn get_stream_ws_url(&self, stream_id: &str) -> String {
// Derive the producer WebSocket URL from the server base URL and stream ID.
// The path follows the asciinema server convention: /ws/S/<stream_id>
let base = self
.config
.api_server
.trim_end_matches('/')
.replace("https://", "wss://")
.replace("http://", "ws://");
format!("{}/ws/S/{}", base, stream_id)
}

fn get_auth_header(&self) -> String {
let cred = format!("user:{}", self.config.install_id);
format!("Basic {}", BASE64_STANDARD.encode(&cred))
}
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New API helpers create_stream, get_stream_ws_url, and get_auth_header were added, but the test module only covers config creation. Adding unit tests for get_stream_ws_url (http/https, trailing slash handling) and get_auth_header (expected Basic base64 format) would help prevent regressions without needing network calls.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/stream.rs
Comment on lines +25 to +28
auth_header: String,
#[allow(dead_code)]
env: HashMap<String, String>,
command: String,
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env is currently stored but never read (suppressed via #[allow(dead_code)]). If it’s not needed for streaming, it’s better to remove it from Stream/Stream::new to keep the command focused; otherwise, consider wiring it into the streamed reset/header data similar to record so it serves a purpose.

Copilot uses AI. Check for mistakes.
@ibigbug
Copy link
Copy Markdown
Member

ibigbug commented Mar 10, 2026

@codex[agent] address pr comments and fix ci

@Codex
Copy link
Copy Markdown
Contributor

Codex AI commented Mar 10, 2026

@codex[agent] address pr comments and fix ci

Addressed the review feedback and fixed the reqwest/json build issue in 30593d2; local cargo test still fails on Linux because windows-only deps require a Windows toolchain.

@ibigbug ibigbug marked this pull request as ready for review March 10, 2026 04:38
@ibigbug
Copy link
Copy Markdown
Member

ibigbug commented Mar 10, 2026

@copilot ci failing. fix it

Co-authored-by: ibigbug <543405+ibigbug@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 10, 2026

@copilot ci failing. fix it

Fixed in 06a4fbd. Three compilation errors in stream.rs:

  • Rustls arm: StreamOwned::get_mut() in rustls 0.23 returns &mut TcpStream directly (not a tuple) — removed the destructure
  • Replaced deprecated read_message() with read()
  • Removed invalid #[cfg(feature = "native-tls")] arm (binary crate has no declared features)

@ibigbug
Copy link
Copy Markdown
Member

ibigbug commented Mar 10, 2026

@copilot build failing

Co-authored-by: ibigbug <543405+ibigbug@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 10, 2026

@copilot build failing

Fixed in 5c60661. MaybeTlsStream is marked #[non_exhaustive] in tungstenite, so the match requires a wildcard arm — added _ => {} to make it exhaustive.

@ibigbug
Copy link
Copy Markdown
Member

ibigbug commented Mar 10, 2026

@claude[agent] fix ci and address pr comments

- Add missing trait imports (base64::Engine, ApiService) in asciinema.rs tests
- Update exit message to be accurate (removed Windows-specific Ctrl+Z instruction)
- PR review comments already addressed: console_size helper, WS read loop, env field removed

Co-authored-by: ibigbug <543405+ibigbug@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor

Claude AI commented Mar 10, 2026

@claude[agent] fix ci and address pr comments

Fixed in 8de0984:

CI fixes:

  • Added missing base64::Engine and ApiService trait imports in test module

PR review comments:

  • Updated exit message to be accurate (removed Windows-specific Ctrl+Z instruction)
  • Note: Other comments were already addressed in previous commits:
    • console_size() helper is used instead of WindowsTerminal::new() (line 241 in main.rs)
    • WebSocket read loop already implemented (lines 101-133 in stream.rs)
    • env field already removed from Stream struct
    • Tests for get_stream_ws_url and get_auth_header already added

@Claude Claude AI changed the title feat: add stream subcommand for live terminal session broadcasting Addressing PR comments Mar 10, 2026
@ibigbug
Copy link
Copy Markdown
Member

ibigbug commented Mar 11, 2026

@copilot update PR title and body

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot update PR title and body

Updated the PR title to feat: add stream subcommand for live terminal session broadcasting and rewrote the body with a full summary of changes: new stream subcommand, extended API client methods, CLI integration, dependencies added, and all review feedback addressed.

Copilot AI changed the title Addressing PR comments feat: add stream subcommand for live terminal session broadcasting Mar 11, 2026
@ibigbug ibigbug enabled auto-merge (squash) March 11, 2026 20:35
@ibigbug ibigbug disabled auto-merge March 11, 2026 20:35
@ibigbug ibigbug merged commit 0d32562 into main Mar 11, 2026
3 checks passed
@ibigbug ibigbug deleted the copilot/support-live-streaming branch March 11, 2026 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support live streaming like asciinema

5 participants